﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

namespace XL_KVMC
{
    public class TCPBase : ICommunications
    {
        private Stopwatch _stopwatch = new Stopwatch();
        protected System.Net.Sockets.Socket _socket;
        public string IP { get; }

        public int Port { get; }

        public int Index { get; }

        public int Timeout { get; set; }
        private Func<ICommunications, CancellationTokenSource, bool> ReconnectDisconnection { get; set; }
        private IProtocol _protocol;
        private CancellationTokenSource _cancellationToken;
        #region Synchronize
        private volatile int _lock = 0;
        protected void Locked()
        {
            int lockNumber = 0;
            while (Interlocked.Exchange(ref _lock, 1) != 0)
            {
                _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0;
                if (++lockNumber > 50)
                {
                    Thread.Sleep(1);
                    lockNumber = 0;
                }
            }
        }

        protected void UnLock() => Interlocked.Exchange(ref _lock, 0);
        #endregion

        public TCPBase(int index, string ip, int port, CancellationTokenSource cancellationToken, int timeout = 3000, Func<ICommunications, CancellationTokenSource, bool> action = null)
        {
            Index = index;
            IP = ip;
            Port = port;
            Timeout = timeout;
            _cancellationToken = cancellationToken;
            ReconnectDisconnection += action;
        }

        public void Close()
        {
            if (_socket != null)
            {
                try
                {
                    _socket.Close();
                    _socket.Dispose();
                }
                catch (Exception)
                {
                }
                _socket = null;
            }
        }

        public bool Open()
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.ReceiveTimeout = 1000;
            _socket.SendTimeout = 1000;
            var ka = new List<byte>(sizeof(uint) * 3);
            ka.AddRange(BitConverter.GetBytes(1u));
            ka.AddRange(BitConverter.GetBytes(45000u));
            ka.AddRange(BitConverter.GetBytes(5000u));
            _socket.IOControl(IOControlCode.KeepAliveValues, ka.ToArray(), null);
            _socket.Connect(new IPEndPoint(IPAddress.Parse(IP), Port));
            return true;
        }

        public byte[] Read(int length)
        {
            if (_socket != null)
            {
                byte[] bytes = new byte[length];
                length = _socket.Receive(bytes, SocketFlags.None);
                return bytes.Take(length).ToArray();
            }
            return default;
        }

        public byte[] SendToRead(byte[] bytes, int count)
        {
            try
            {
                Locked();
                _socket.Send(_protocol.Serialize(bytes));
                List<byte> list = new List<byte>();
                byte[] buffer = new byte[1024];
                int length = _socket.Receive(buffer);
                list.AddRange(buffer.Take(length));
                while (!_protocol.Verify(list))
                {
                    length = _socket.Receive(buffer);
                    list.AddRange(buffer.Take(length));
                }
                UnLock();
                return _protocol.Deserialize(list);
            }
            catch (Exception ex)
            {
                UnLock();
                if (count == 1
                    || _cancellationToken.Token.IsCancellationRequested)
                {
                    throw ex;
                }
                while (!ReconnectDisconnection.Invoke(this, _cancellationToken) && !_cancellationToken.Token.IsCancellationRequested)
                {
                    count--;
                    if (count == 0)
                    {
                        throw new NotImplementedException("重连失败");
                    }
                }
                return SendToRead(bytes, count--);
            }
        }

        public void Write(byte[] bytes) => _socket?.Send(bytes, SocketFlags.None);

        public void SetProtocol(IProtocol protocol) => _protocol = protocol;
    }
}
